{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7609a68a-c01b-43a8-90aa-3d004af614dd",
   "metadata": {},
   "source": [
    "# Sharing access to a server\n",
    "\n",
    "This notebook executes some javascript in the browser, using the user's OAuth token.\n",
    "\n",
    "This code would normally reside in a jupyterlab extension.\n",
    "The notebook serves only for demonstration purposes.\n",
    "\n",
    "First, collect some configuration from the page, so we can talk to the JupyterHub API:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "b3cf16bd-ff9b-4140-a394-5a7ca5d96d88",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "// define some globals to share\n",
       "\n",
       "var configElement = document.getElementById(\"jupyter-config-data\");\n",
       "var jupyterConfig = JSON.parse(configElement.innerHTML);\n",
       "\n",
       "window.token = jupyterConfig.token;\n",
       "window.hubOrigin = `${document.location.protocol}//${jupyterConfig.hubHost || window.location.host}`\n",
       "window.hubUrl = `${hubOrigin}${jupyterConfig.hubPrefix}`;\n",
       "window.shareCodesUrl = `${hubUrl}api/share-codes/${jupyterConfig.hubServerUser}/${jupyterConfig.hubServerName}`\n",
       "window.sharesUrl = `${hubUrl}api/shares/${jupyterConfig.hubServerUser}/${jupyterConfig.hubServerName}`\n",
       "console.log(shareCodesUrl);\n",
       "\n",
       "// utility function to make API requests and parse errors\n",
       "window.apiRequest = async function (url, options) {\n",
       "  var element = options.element;\n",
       "  var okStatus = options.ok || 200;\n",
       "  var resp = await fetch(url, {headers: {Authorization: `Bearer ${token}`}, method: options.method || 'GET'});\n",
       "  var replyText = await resp.text();\n",
       "  var replyJSON = {};\n",
       "  if (replyText.length) {\n",
       "     replyJSON = JSON.parse(replyText);\n",
       "  }\n",
       "  \n",
       "  if (resp.status != okStatus) {\n",
       "    var p = document.createElement('p');\n",
       "    p.innerText = `Error ${resp.status}: ${replyJSON.message}`;\n",
       "    element.appendChild(p);\n",
       "    return;\n",
       "  }\n",
       "  return replyJSON;\n",
       "}\n",
       "\n",
       "// `element` is a special variable for the current cell's output area\n",
       "element.innerText = `API URL for sharing codes is: ${shareCodesUrl}`;\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%javascript\n",
    "// define some globals to share\n",
    "\n",
    "var configElement = document.getElementById(\"jupyter-config-data\");\n",
    "var jupyterConfig = JSON.parse(configElement.innerHTML);\n",
    "\n",
    "window.token = jupyterConfig.token;\n",
    "window.hubOrigin = `${document.location.protocol}//${jupyterConfig.hubHost || window.location.host}`\n",
    "window.hubUrl = `${hubOrigin}${jupyterConfig.hubPrefix}`;\n",
    "window.shareCodesUrl = `${hubUrl}api/share-codes/${jupyterConfig.hubServerUser}/${jupyterConfig.hubServerName}`\n",
    "window.sharesUrl = `${hubUrl}api/shares/${jupyterConfig.hubServerUser}/${jupyterConfig.hubServerName}`\n",
    "console.log(shareCodesUrl);\n",
    "\n",
    "// utility function to make API requests and parse errors\n",
    "window.apiRequest = async function (url, options) {\n",
    "  var element = options.element;\n",
    "  var okStatus = options.ok || 200;\n",
    "  var resp = await fetch(url, {headers: {Authorization: `Bearer ${token}`}, method: options.method || 'GET'});\n",
    "  var replyText = await resp.text();\n",
    "  var replyJSON = {};\n",
    "  if (replyText.length) {\n",
    "     replyJSON = JSON.parse(replyText);\n",
    "  }\n",
    "  \n",
    "  if (resp.status != okStatus) {\n",
    "    var p = document.createElement('p');\n",
    "    p.innerText = `Error ${resp.status}: ${replyJSON.message}`;\n",
    "    element.appendChild(p);\n",
    "    return;\n",
    "  }\n",
    "  return replyJSON;\n",
    "}\n",
    "\n",
    "// `element` is a special variable for the current cell's output area\n",
    "element.innerText = `API URL for sharing codes is: ${shareCodesUrl}`;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2049fa1-bb60-4073-9167-2e116b198f0e",
   "metadata": {},
   "source": [
    "Next, we can request a share code with\n",
    "\n",
    "```\n",
    "POST $hub/api/share-codes/$user/$server\n",
    "```\n",
    "\n",
    "The URL for _accepting_ a sharing invitation code is `/hub/accept-share?code=abc123...`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "ee9430f3-4866-41f7-942d-423bd48dc6b8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "(async function f() {\n",
       "  var shareCode = await apiRequest(shareCodesUrl, {method: 'POST', element: element});\n",
       "\n",
       "  // laziest way to display\n",
       "  var shareCodeUrl = `${hubOrigin}${shareCode.accept_url}`\n",
       "  var a = document.createElement('a');\n",
       "  a.href = shareCodeUrl;\n",
       "  a.innerText = shareCodeUrl;\n",
       "  var p = document.createElement(p);\n",
       "  p.append(\"Share this URL to grant access to this server: \");\n",
       "  p.appendChild(a);\n",
       "  element.appendChild(p);\n",
       "})();\n",
       "\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%javascript\n",
    "\n",
    "(async function f() {\n",
    "  var shareCode = await apiRequest(shareCodesUrl, {method: 'POST', element: element});\n",
    "\n",
    "  // laziest way to display\n",
    "  var shareCodeUrl = `${hubOrigin}${shareCode.accept_url}`\n",
    "  var a = document.createElement('a');\n",
    "  a.href = shareCodeUrl;\n",
    "  a.innerText = shareCodeUrl;\n",
    "  var p = document.createElement(p);\n",
    "  p.append(\"Share this URL to grant access to this server: \");\n",
    "  p.appendChild(a);\n",
    "  element.appendChild(p);\n",
    "})();\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "418152a0-2e0f-4ca6-8b53-9295d15c4345",
   "metadata": {},
   "source": [
    "Share this URL to grant access to your server (e.g. visit the URL in a private window and login as the user `shared-with`).\n",
    "\n",
    "After our code has been used, we can see who has access to this server:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5c9c6e1b-ccab-4c85-8afb-db141651236a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "(async function f() {\n",
       "\n",
       "  var shares = await apiRequest(sharesUrl, {element: element});\n",
       "\n",
       "  var list = document.createElement('ul');\n",
       "  for (var share of shares.items) {\n",
       "    var p = document.createElement('li');\n",
       "    p.append(`${share.kind} ${share[share.kind].name} has access: `)\n",
       "    var scopes = document.createElement('tt');\n",
       "    scopes.innerText = share.scopes.join(',');\n",
       "    p.appendChild(scopes);\n",
       "    list.append(p);\n",
       "  }\n",
       "  var p = document.createElement('p');\n",
       "  p.innerText = `Shared with ${shares.items.length} users:`;\n",
       "  element.appendChild(p);\n",
       "  element.appendChild(list);\n",
       "  return;\n",
       "})();\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%javascript\n",
    "\n",
    "(async function f() {\n",
    "\n",
    "  var shares = await apiRequest(sharesUrl, {element: element});\n",
    "\n",
    "  var list = document.createElement('ul');\n",
    "  for (var share of shares.items) {\n",
    "    var p = document.createElement('li');\n",
    "    p.append(`${share.kind} ${share[share.kind].name} has access: `)\n",
    "    var scopes = document.createElement('tt');\n",
    "    scopes.innerText = share.scopes.join(',');\n",
    "    p.appendChild(scopes);\n",
    "    list.append(p);\n",
    "  }\n",
    "  var p = document.createElement('p');\n",
    "  p.innerText = `Shared with ${shares.items.length} users:`;\n",
    "  element.appendChild(p);\n",
    "  element.appendChild(list);\n",
    "  return;\n",
    "})();\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18165205-67f3-44d4-ad6b-40ab27ec469c",
   "metadata": {},
   "source": [
    "We could also use this info to revoke permissions, or share with individuals by name.\n",
    "\n",
    "We can also review outstanding sharing _codes_:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "40cd1e2d-fc21-4238-8cbc-9ae45be248c9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "(async function f() {\n",
       "  var shareCodes = await apiRequest(shareCodesUrl, {element: element});\n",
       "  var p = document.createElement('pre');\n",
       "  p.innerText = JSON.stringify(shareCodes.items, null, ' ');\n",
       "  element.appendChild(p);\n",
       "})();\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%javascript\n",
    "\n",
    "(async function f() {\n",
    "  var shareCodes = await apiRequest(shareCodesUrl, {element: element});\n",
    "  var p = document.createElement('pre');\n",
    "  p.innerText = JSON.stringify(shareCodes.items, null, ' ');\n",
    "  element.appendChild(p);\n",
    "})();\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0effee22-c132-4b44-a677-c4375594c462",
   "metadata": {},
   "source": [
    "And finally, when we're done, we can revoke the codes, at which point nobody _new_ can use the code to gain access to this server,\n",
    "but anyone who has accepted the code will still have access:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "51a795f5-9a74-49b1-9ef3-e7978da0cc44",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "(async function f() {\n",
       "  await apiRequest(shareCodesUrl, {method: 'DELETE', element: element, ok: 204});\n",
       "  var p = document.createElement('p');\n",
       "  p.innerText = `Deleted all share codes`;\n",
       "  element.appendChild(p);  \n",
       "})();\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%javascript\n",
    "\n",
    "(async function f() {\n",
    "  await apiRequest(shareCodesUrl, {method: 'DELETE', element: element, ok: 204});\n",
    "  var p = document.createElement('p');\n",
    "  p.innerText = `Deleted all share codes`;\n",
    "  element.appendChild(p);  \n",
    "})();"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ad12718-1f68-4d2f-9934-dd6bd4555a1d",
   "metadata": {},
   "source": [
    "Or even revoke all shared access, so anyone who may have used the code no longer has any access:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "efa5ae15-ac99-4bf4-b7b4-3d93747dba8c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "(async function f() {\n",
       "  var resp = await apiRequest(sharesUrl, {method: 'DELETE', element: element, ok: 204});\n",
       "  var p = document.createElement('p');\n",
       "  p.innerText = `Deleted all shared access`;\n",
       "  element.appendChild(p);  \n",
       "})();\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%javascript\n",
    "\n",
    "(async function f() {\n",
    "  var resp = await apiRequest(sharesUrl, {method: 'DELETE', element: element, ok: 204});\n",
    "  var p = document.createElement('p');\n",
    "  p.innerText = `Deleted all shared access`;\n",
    "  element.appendChild(p);  \n",
    "})();"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
